/*
 * Ext Gantt 1.2
 * Copyright(c) 2009-2011 Mats Bryntse Consulting
 * mats@ext-scheduler.com
 * http://www.ext-scheduler.com/license.html
 *
 */

Ext.ns('Sch.gantt');

/**
 * @class Sch.gantt.DependencyDragDrop
 * @extends Ext.util.Observable
 * Internal class managing the interaction with the dependency terminals.
 * @constructor
 * @param {Object} config The object containing the configuration of this model.
 */
Sch.gantt.DependencyDragDrop = function(gantt) {
    this.addEvents(
        /**
         * @event beforednd
         * Fires before a drag and drop operation is initiated, return false to cancel it
         * @param {Sch.gantt.DependencyDragDrop} dnd The drag and drop instance
         * @param {Ext.data.Record} fromRecord The task record 
         */ 
        'beforednd', 
        
        /**
         * @event dndstart
         * Fires when a drag and drop operation starts
         * @param {Sch.gantt.DependencyDragDrop} dnd The drag and drop instance
         */
        'dndstart',
        
        /**
         * @event drop
         * Fires after a drop has been made on a receiving terminal
         * @param {Sch.gantt.DependencyDragDrop} dnd The drag and drop instance
         * @param {Mixed} fromId The source dependency task record id
         * @param {Mixed} toId The target dependency task record id
         * @param {Int} type The dependency type, see sch.dependencymanager.js for more information
         */
        'drop',

        /**
         * @event afterdnd
         * Always fires after a dependency drag and drop operation
         * @param {Sch.gantt.DependencyDragDrop} dnd The drag and drop instance
         */
        'afterdnd'
    );
    
    var view = gantt.getView();
    
    Ext.apply(this, {
        el : view.mainBody,
        scroller : view.scroller,
        ddGroup : gantt.id + '-sch-dependency-dd',
        gantt : gantt,
        dependencyStore : gantt.getDependencyStore()
    });

    this.setupDragZone();
    this.setupDropZone();

    Sch.gantt.DependencyDragDrop.superclass.constructor.call(this);
};

Ext.extend(Sch.gantt.DependencyDragDrop, Ext.util.Observable, {
    
    /**
     * @cfg {String} fromText The text to show before the from task when setting up a dependency. Defaults to 'From:'.
     */
    fromText : 'From: <strong>{0}</strong> {1}<br/>',
    
    /**
     * @cfg {String} toText The text to show before the to task when setting up a dependency. Defaults to 'From:'.
     */
    toText : 'To: <strong>{0}</strong> {1}',
    
    /**
     * @cfg {String} startText The text indicating that a dependency connector is a Start type.
     */
    startText : 'Start',
    
    /**
     * @cfg {String} endText The text indicating whether a dependency connector is an End type.
     */
    endText : 'End',
    
    /**
     * @cfg {Boolean} useLineProxy True to display a line while dragging
     */
    useLineProxy : true,
    
    // private, the terminal CSS selector
    terminalSelector : '.sch-gantt-terminal',
    
    isValidDrop : function(fromTaskId, toTaskId) {
        // Make sure a) the drop is not made on the same task, and b) no previous dependency exists between the two tasks
        function find (r) {
            return fromTaskId === toTaskId ||
                   r.get('From') === fromTaskId && r.get('To') === toTaskId ||
                   r.get('To') === fromTaskId && r.get('From') === toTaskId;
        }
        
        return this.dependencyStore.findBy(find) < 0;
    },
    
    destroy : function() {
        this.dragZone.destroy();
        this.dropZone.destroy();

        if (this.lineProxyEl) {
            this.lineProxyEl.destroy();
        }
    },

    initLineProxy : function(sourceEl, isStart) {
        var lpEl = this.lineProxyEl = this.el.createChild({ cls : 'sch-gantt-connector-proxy' }); 

        lpEl.alignTo(sourceEl, isStart ? 'l' : 'r');
        
        Ext.apply(this, {
            containerTop : this.el.getTop(),
            containerLeft : this.el.getLeft(),
            startXY : lpEl.getXY(),
            startScrollLeft : this.scroller.dom.scrollLeft,
            startScrollTop : this.scroller.dom.scrollTop
        });
    },

    updateLineProxy : function(xy) {
        var lineProxy = this.lineProxyEl,
            diffX = xy[0] - this.startXY[0] + this.scroller.dom.scrollLeft - this.startScrollLeft,
            diffY = xy[1] - this.startXY[1] + this.scroller.dom.scrollTop - this.startScrollTop,
            newHeight = Math.max(1, Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2)) - 2),

            // Calculate new angle relative to start XY
            rad = Math.atan2(diffY, diffX) - (Math.PI/2),
            styleBlob;

        if (Ext.isIE) {
            var cos = Math.cos(rad),
                sin = Math.sin(rad),
                matrixString = 'progid:DXImageTransform.Microsoft.Matrix(sizingMethod="auto expand", M11 = ' + cos + ', M12 = ' + (-sin) + ', M21 = ' + sin + ', M22 = ' + cos + ')';
            
            styleBlob = {
                "height"    : newHeight + 'px',
                "top"       : Math.min(0, diffY) + this.startXY[1]  - this.containerTop + (diffY < 0 ? 2 : 0)  + 'px',
                "left"      : Math.min(0, diffX) + this.startXY[0] - this.containerLeft + (diffX < 0 ? 2 : 0) + 'px',
                "filter"    : matrixString,
                "-ms-filter": matrixString
            };
        } else {
            var rotateString = 'rotate(' + rad + 'rad)';
            
            styleBlob = {
                "height"            : newHeight + 'px',
                "-o-transform"      : rotateString,
                "-webkit-transform" : rotateString,
                "-moz-transform"    : rotateString,
                "transform"         : rotateString
            };
        }
        
        lineProxy.show().setStyle(styleBlob);
    },

    // private
    setupDragZone : function() {
        var me = this,
            g = this.gantt;
        
        // The drag zone behaviour
        this.dragZone = new Ext.dd.DragZone(this.el, {
            ddGroup : this.ddGroup,
            
            onStartDrag : function () {
                this.el.addClass('sch-gantt-dep-dd-dragging');
                me.fireEvent('dndstart', me);

                if (me.useLineProxy) {
                    var dd = this.dragData;
                    me.initLineProxy(dd.sourceNode, dd.isStart);
                }
            },
            
            // On receipt of a mousedown event, see if it is within a draggable element.
            // Return a drag data object if so. The data object can contain arbitrary application
            // data, but it should also contain a DOM element in the ddel property to provide
            // a proxy to drag.
            getDragData: function(e) {
                var sourceNode = e.getTarget(me.terminalSelector);

                if (sourceNode) {
                    var sourceTaskRecord = g.resolveTask(sourceNode);
                    if (me.fireEvent('beforednd', this, sourceTaskRecord) === false) {
                        return null;
                    }
                    
                    var isStart = !!sourceNode.className.match('sch-gantt-terminal-start'),
                        ddel = Ext.DomHelper.createDom({
                            cls: 'sch-dd-dependency',
                            children: [
                                {
                                    tag: 'span', 
                                    cls: 'sch-dd-dependency-from', 
                                    html: String.format(me.fromText, sourceTaskRecord.get('Name'), isStart ? me.startText : me.endText)
                                },
                                {
                                    tag: 'span', 
                                    cls: 'sch-dd-dependency-to', 
                                    html: String.format(me.toText, '', '')
                                }
                            ]
                        });
                    
                    return {
                        fromId : sourceTaskRecord.id,
                        isStart : isStart,
                        repairXY: Ext.fly(sourceNode).getXY(),
                        ddel: ddel,
                        sourceNode : Ext.fly(sourceNode).up(g.eventSelector)
                    };
                }
                return false;
            },
            
            // Override, get rid of weird highlight fx in default implementation
            afterRepair : function(){
                this.el.removeClass('sch-gantt-dep-dd-dragging');
                
                this.dragging = false;
                me.fireEvent('afterdnd', this);
            },

            onMouseUp : function() {
                if (me.lineProxyEl) {
                    if (Ext.isIE) {
                        Ext.destroy(me.lineProxyEl);
                        me.lineProxyEl = null;
                    }
                    else {
                        me.lineProxyEl.setHeight(0, {
                            duration : .3, 
                            callback : function() {
                                Ext.destroy(me.lineProxyEl);
                                me.lineProxyEl = null;
                            }
                        });
                    }
                }
            },

            // Provide coordinates for the proxy to slide back to on failed drag.
            // This is the original XY coordinates of the draggable element.
            getRepairXY: function() {
                return this.dragData.repairXY;
            }
        });
    },
    
    // private
    setupDropZone : function () {
        var me = this,
            g = this.gantt;
                    
        // The drop zone behaviour
        this.dropZone = new Ext.dd.DropZone(this.el, {
            ddGroup : this.ddGroup,
            
            getTargetFromEvent: function(e) {
                if (me.useLineProxy) {
                    me.updateLineProxy(e.getXY());
                }
                return e.getTarget(me.terminalSelector);
            },
            
            // On entry into a target node, highlight that node.
            onNodeEnter : function(target, dd, e, data){
                var isTargetStart = target.className.match('sch-gantt-terminal-start');
                Ext.fly(target).addClass(isTargetStart ? 'sch-gantt-terminal-start-drophover' : 'sch-gantt-terminal-end-drophover');
            },

            // On exit from a target node, unhighlight that node.
            onNodeOut : function(target, dd, e, data){
                var isTargetStart = target.className.match('sch-gantt-terminal-start');
                Ext.fly(target).removeClass(isTargetStart ? 'sch-gantt-terminal-start-drophover' : 'sch-gantt-terminal-end-drophover');
            },
            
            onNodeOver : function(target, dd, e, data){
                var targetRecord = g.resolveTask(target),
                    targetId = targetRecord.id,
                    isTargetStart = target.className.match('sch-gantt-terminal-start'),
                    newText = String.format(me.toText, targetRecord.get('Name'), isTargetStart ? me.startText : me.endText);
                
                dd.proxy.el.child('.sch-dd-dependency-to').update(newText);
                
                if (me.isValidDrop.call(me, data.fromId, targetId)) {
                    return this.dropAllowed;
                } else {
                    return this.dropNotAllowed;
                }
            },
            
            onNodeDrop : function(target, dd, e, data){
                var type, 
                    retVal = true,
                    rec = g.resolveTask(target),
                    targetId = rec.id;
                
                if (me.lineProxyEl) {
                    Ext.destroy(me.lineProxyEl);
                    me.lineProxyEl = null;
                }
                this.el.removeClass('sch-gantt-dep-dd-dragging');
                
                if (data.isStart) {
                    if (target.className.match('sch-gantt-terminal-start')) {
                        type = Sch.gantt.Dependency.StartToStart;
                    } else {
                        type = Sch.gantt.Dependency.StartToEnd;
                    }
                } else {
                    if (target.className.match('sch-gantt-terminal-start')) {
                        type = Sch.gantt.Dependency.EndToStart;
                    } else {
                        type = Sch.gantt.Dependency.EndToEnd;
                    }
                }
               
                retVal = me.isValidDrop.call(me, data.fromId, targetId, true);
                
                if (retVal) {
                    me.fireEvent('drop', this, data.fromId, rec.id, type);
                }
                me.fireEvent('afterdnd', this);
                return retVal;
            }
        });
    }
});